package com.starview.star.compiler.type;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import com.starview.platform.data.type.IType;
import com.starview.platform.data.type.TypeExp;
import com.starview.platform.data.type.TypeUtils;
import com.starview.star.compiler.canonical.DefaultVisitor;
import com.starview.star.compiler.canonical.IStatement;
import com.starview.star.compiler.canonical.MethodVariable;
import com.starview.star.compiler.canonical.VarEntry;
import com.starview.star.compiler.canonical.Variable;
import com.starview.star.compiler.transform.OverLoader;
import com.starview.star.compiler.util.PrettyPrintDisplay;
import com.starview.star.compiler.util.PrettyPrintable;
import com.starview.star.compiler.util.TopologySort;
import com.starview.star.compiler.util.TopologySort.IDefinition;

@SuppressWarnings("serial")
public class ContractDependencies
{
  public static List<List<IStatement>> dependencySort(List<IStatement> stmts)
  {
    List<String> allList = pickList(stmts);
    List<Implementation> initGroup = new ArrayList<Implementation>();
    for (IStatement def : stmts)
      initGroup.add(new Implementation(def, allList));
    List<List<IDefinition<String>>> groups = new TopologySort<String>(initGroup).sort();
    List<List<IStatement>> sortd = new ArrayList<List<IStatement>>();
    for (List<IDefinition<String>> entry : groups) {
      List<IStatement> group = new ArrayList<IStatement>();
      sortd.add(group);
      for (IDefinition<String> def : entry)
        group.add(((Implementation) def).getStmt());
    }

    return sortd;
  }

  private static List<String> pickList(List<IStatement> stmts)
  {
    List<String> pick = new ArrayList<String>();

    for (IStatement stmt : stmts) {
      if (stmt instanceof VarEntry) {
        VarEntry var = (VarEntry) stmt;
        for (Variable n : var.getDefined())
          if (!pick.contains(n.getName()))
            pick.add(n.getName());
      }
    }
    return pick;
  }

  @SuppressWarnings("unused")
  private static <T> boolean setEqual(Collection<T> S1, Collection<T> S2)
  {
    if (S1.size() != S2.size())
      return false;
    else {
      outer: for (T s1 : S1) {
        for (T s2 : S2)
          if (s1.equals(s2))
            continue outer;
        return false;
      }
      return true;
    }
  }

  private static class Implementation implements IDefinition<String>, PrettyPrintable
  {
    private final IStatement stmt;
    private final List<String> refs;
    private final List<String> defs;

    public Implementation(IStatement stmt, List<String> pick)
    {
      this.stmt = stmt;
      if (stmt instanceof VarEntry) {
        this.defs = Variable.varNames(((VarEntry) stmt).getDefined());
        this.refs = allOverloadRefs(stmt, pick);
      } else {
        this.defs = new ArrayList<String>();
        this.refs = new ArrayList<String>();
      }
    }

    public IStatement getStmt()
    {
      return stmt;
    }

    @Override
    public void prettyPrint(PrettyPrintDisplay disp)
    {
      disp.append(defs.toString());
      disp.append(":");
      disp.append(refs.toString());
    }

    @Override
    public String toString()
    {
      return PrettyPrintDisplay.toString(this);
    }

    @Override
    public boolean defines(String obj)
    {
      return defs.contains(obj);
    }

    @Override
    public List<String> references()
    {
      return refs;
    }
  }

  private static List<String> allOverloadRefs(IStatement stmt, List<String> pick)
  {
    OverloadFinder finder = new OverloadFinder(pick);

    stmt.accept(finder);
    return finder.refs;
  }

  private static class OverloadFinder extends DefaultVisitor
  {
    private final List<String> pick;
    private List<String> refs = new ArrayList<String>();

    public OverloadFinder(List<String> pick)
    {
      super(true);
      this.pick = pick;
    }

    @Override
    public void visitVariable(Variable variable)
    {
      if (!isExcluded(variable.getName())) {
        if (variable instanceof MethodVariable) {
          MethodVariable method = (MethodVariable) variable;

          IType contract = TypeUtils.deRef(method.getContract());
          if (contract instanceof TypeExp) {
            String funName = OverLoader.instanceFunName(contract);

            if (pick.contains(funName) && !refs.contains(funName))
              refs.add(funName);
          }
        } else {
          String name = variable.getName();
          if (pick.contains(name) && !refs.contains(name))
            refs.add(name);
        }
      }
    }

    @Override
    public void visitVarEntry(VarEntry entry)
    {
      entry.getValue().accept(this);
    }
  }
}
